import { Scene } from '@babylonjs/core/scene';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { IUITextOptions, UIText } from './ui-text';
import { Calculation } from '../../tool/calculation';
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
import { CreateHandleTo, IHandle, RemoveHandle } from '../../data/data-handle';
import { BoundingBox } from '@babylonjs/core/Culling/boundingBox';
import { ANCHOR, AbstractBase, IAbstractBaseOptions } from './abstract-base';
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';

/**
 * 抽象控件组参数对象
 * @param isVertical 是否垂直布局
 * @param headerOptions 页眉参数对象
 * @param itemOptions 成员参数对象
 * @param autoLayout 是否自动布局
 * @param spacing 间隔留白
 * @param tittleOffset 页眉偏移
 * @param ignoreTitleLayout 布局时忽略页眉
 */
interface IAbstractGroupOptions<T> extends IAbstractBaseOptions {
    isVertical?: boolean,
    headerOptions?: IUITextOptions,
    itemsOptions?: Array<T>,
    autoLayout?: boolean,
    spacing?: number,
    titleOffset?: { x: number, y: number },
    ignoreTitleLayout?: boolean,
}


/**
 * 控件组抽象类
 * @param name 名称
 * @param scene 所在场景
 * @param options 参数对象
 */
abstract class AbstractGroup extends AbstractBase {

    private _tv0 = new Vector3();
    private _tv1 = new Vector3();

    protected fontSize!: number;
    protected fontColor!: string;
    protected fontFamily!: string;
    protected boundMax = new Vector3();
    protected boundMin = new Vector3();
    protected changeHandles: Array<IHandle<any>> = [];
    protected boundBox = new BoundingBox(this.boundMin, this.boundMax);

    /**
     * 页眉（标题）
     */
    public header!: UIText;

    /**
     * 成员控件集合
     */
    public items: Array<AbstractBase> = [];

    constructor(name: string, scene: Scene, options?: IAbstractGroupOptions<IAbstractBaseOptions>) {
        super(name, scene, options);
        this.initialize(name, scene, options || {});
    }

    protected initialize(name: string, scene: Scene, ops: IAbstractGroupOptions<IAbstractBaseOptions>): void {
        this.container = new TransformNode(name + 'container', scene);
        this.setContainer(ops);
        this.header = this.initializeHeader(name, scene, ops.headerOptions || {});
        this.items = this.initializeItems(name, scene, ops.itemsOptions || [], ops);
        this.autoLayout(ops);
        this.setAnchor(ops);
        this.display = ops.display === undefined ? true : ops.display;
        if (ops.renderingGroupId !== undefined) this.renderingGroupId = ops.renderingGroupId;
    }

    public get isControl(): boolean {
        return false;
    }

    public set isEnabled(isEnabled: boolean) {
        this.header.isEnabled = isEnabled;
        this.items.forEach(item => item.isEnabled = isEnabled);
    }

    public get isEnabled(): boolean {
        return this.header.isEnabled;
    }

    public set left(left: number) {
        this.container.position.x = left;
    }

    public get left(): number {
        return this.container.position.x;
    }

    public set top(top: number) {
        this.container.position.y = top;
    }

    public get top(): number {
        return this.container.position.y;
    }

    public set depth(depth: number) {
        this.container.position.z = depth;
    }

    public get depth(): number {
        return this.container.position.z;
    }

    /**
     * 获取宽度
     */
    public get width(): number {
        return this.boundBox.extendSize.x * 2;
    }

    /**
     * 获取高度
     */
    public get height(): number {
        return this.boundBox.extendSize.y * 2;
    }

    /**
     * 获取包围盒
     */
    public get boundingBox(): BoundingBox {
        return this.boundBox;
    }

    public set display(display: boolean) {
        const s = display ? 1 : 0;
        this.container.scaling.set(s, s, s);
    }

    public get display(): boolean {
        return !!this.container.scaling.x;
    }

    /**
     * 添加改变时的回掉函数
     * @param func 回掉函数
     * @returns 回掉函数对象
     */
    public onChange(func: { (value: any): void }): IHandle<any> {
        return CreateHandleTo(func, this.changeHandles);
    }

    /**
     * 移除改变时的回调函数对象
     * @param handle 回掉函数对象或其id
     */
    public removeChangeHandle(handle: number | IHandle<any>): void {
        RemoveHandle(handle, this.changeHandles);
    }

    public set renderingGroupId(id: number) {
        if (id === undefined) return;
        this.header.renderingGroupId = id;
        this.items.forEach(item => item.renderingGroupId = id);
    }

    public get renderingGroupId(): number {
        return this.header.renderingGroupId;
    }

    /**
     * 设置锚点
     * @param options 参数对象
     */
    private setAnchor(options: IAbstractGroupOptions<IAbstractBaseOptions>): void {
        const header = this.header;
        const items = this.items;
        const boundPoints: Vector3[] = [];
        const children = options.ignoreTitleLayout ? items : [header, ...items];
        for (const ctn of children) {
            const eps = ctn.isControl ?
                (ctn.container as AbstractMesh).getBoundingInfo().boundingBox.vectors :
                (ctn as AbstractGroup).boundingBox.vectors;
            const scl = ctn.container.scaling;
            const pos = ctn.container.position;
            for (const ep of eps) {
                const p = ep.clone();
                p.x *= scl.x;
                p.y *= scl.y;
                p.addInPlace(pos);
                boundPoints.push(p);
            }
        }

        Calculation.boundingMaxMin(boundPoints, this.boundMax, this.boundMin);
        this.boundBox.reConstruct(this.boundMin, this.boundMax);
        const cent = this._tv0.copyFrom(this.boundBox.center);
        const extW = this.boundBox.extendSize.x;
        const extH = this.boundBox.extendSize.y;
        const anchor = options.anchor || ANCHOR.CENTER;
        const offset = this._tv1.copyFrom(cent).negateInPlace();
        switch (anchor) {
            case ANCHOR.LEFT_CENTER:
                offset.x += extW;
                break;
            case ANCHOR.RIGHT_CENTER:
                offset.x -= extW;
                break;
            case ANCHOR.CENTER_TOP:
                offset.y -= extH;
                break;
            case ANCHOR.CENTER_BOTTOM:
                offset.y += extH;
                break;
            case ANCHOR.LEFT_TOP:
                offset.x += extW;
                offset.y -= extH;
                break;
            case ANCHOR.RIGHT_TOP:
                offset.x -= extW;
                offset.y -= extH;
                break;
            case ANCHOR.LEFT_BOTTOM:
                offset.x += extW;
                offset.y += extH;
                break;
            case ANCHOR.RIGHT_BOTTOM:
                offset.x -= extW;
                offset.y += extH;
                break;
        }
        for (const child of children) {
            child.left += offset.x;
            child.top += offset.y;
        }
        this.boundMax.addInPlace(offset);
        this.boundMin.addInPlace(offset);
        this.boundBox.reConstruct(this.boundMin, this.boundMax);

    }

    /**
     * 自动布局
     * @param options 参数对象
     */
    private autoLayout(options: IAbstractGroupOptions<IAbstractBaseOptions>): void {
        if (!options.autoLayout) return;
        const space = options.spacing || 0;
        const isVertical = !!options.isVertical;
        const hOffset = options.titleOffset || { x: 0, y: 0 };
        const header = this.header;
        const items = this.items;
        const headerExt = (header.container as AbstractMesh).getBoundingInfo().boundingBox.extendSize;
        const hw = headerExt.x * 2 * header.container.scaling.x;
        const hh = headerExt.y * 2 * header.container.scaling.y;
        header.left = hw * 0.5 + hOffset.x;
        header.top = (isVertical ? -hh * 0.5 : 0) + hOffset.y;
        let curT = options.ignoreTitleLayout ? 0 : -hh;
        let curL = options.ignoreTitleLayout ? 0 : hw;
        for (const item of items) {
            const itemExt = item.isControl ?
                (item.container as AbstractMesh).getBoundingInfo().boundingBox.extendSize :
                (item as AbstractGroup).boundingBox.extendSize;
            const iw = itemExt.x * 2 * item.container.scaling.x;
            const ih = itemExt.y * 2 * item.container.scaling.y;
            if (isVertical) {
                item.left = iw * 0.5;
                item.top = curT - space - ih * 0.5;
                curT = item.top - ih * 0.5;
            } else {
                item.left = curL + space + iw * 0.5;
                item.top = 0;
                curL = item.left + iw * 0.5;
            }
        }

    }

    /**
     * 初始化页眉（标题）
     * @param name 名称
     * @param scene 所在场景
     * @param options 参数对象
     * @returns 文字控件
     */
    private initializeHeader(name: string, scene: Scene, options: IUITextOptions): UIText {
        const header = new UIText(name + '-header', scene, options);
        header.container.parent = this.container;
        return header;
    }

    /**
     * 初始化成员控件
     * @param name 名称
     * @param scene 所在场景
     * @param itemsOptions 成员控件参数对象
     * @param groupOptions 组控件参数对象
     * @returns 控件集合
     */
    protected initializeItems(name: string, scene: Scene, itemsOptions: IAbstractBaseOptions[], groupOptions: IAbstractGroupOptions<IAbstractBaseOptions>): Array<AbstractBase> {
        const items: Array<AbstractBase> = [];
        for (let i = 0; i < itemsOptions.length; i++) {
            const item = this.generateItem(name + '-item-' + i, scene, itemsOptions[i], groupOptions);
            if (!item) continue;
            item.container.parent = this.container;
            items.push(item);
        }
        return items;
    }

    /**
     * 创建单个成员控件
     * @param name 名称
     * @param scene 所在场景
     * @param itemOptions 成员控件参数对象
     * @param groupOptions 组控件参数对象
     * @returns 控件
     */
    protected abstract generateItem(name: string, scene: Scene, itemOptions: IAbstractBaseOptions, groupOptions: IAbstractGroupOptions<IAbstractBaseOptions>): AbstractBase | null;

    /**
     * 设置容器
     * @param options 参数对象
     */
    private setContainer(options: IAbstractGroupOptions<IAbstractBaseOptions>): void {
        this.container.position.x = options.left || 0;
        this.container.position.y = options.top || 0;
        this.container.position.z = options.depth || 0;
    }

}


export { AbstractGroup };
export type { IAbstractGroupOptions };